package org.andengine.opengl.vbo;

import org.andengine.opengl.util.BufferUtils;
import org.andengine.opengl.vbo.attribute.VertexBufferObjectAttributes;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Compared to {@link ZeroMemoryVertexBufferObject}, all {@link SharedMemoryVertexBufferObject}s share a single {@link ByteBuffer} which is used by whichever {@link SharedMemoryVertexBufferObject} instance is currently buffering data,
 * at the cost of expensive data buffering (<b>up to <u>5x</u> slower!</b>) and a little synchronization overhead.
 * <p/>
 * Usually a {@link SharedMemoryVertexBufferObject} is preferred to a {@link ZeroMemoryVertexBufferObject} when the following conditions need to be met:
 * <ol>
 * <li>Minimum amount of runtime GarbageCollector activity.</li>
 * </ol>
 * <p/>
 * (c) Zynga 2011
 *
 * @author Nicolas Gramlich <ngramlich@zynga.com>
 * @author Greg Haynes
 * @since 19:22:13 - 10.02.2012
 */
public abstract class SharedMemoryVertexBufferObject extends ZeroMemoryVertexBufferObject {
    // ===========================================================
    // Constants
    // ===========================================================

    private static ReentrantLock sSharedByteBufferLock = new ReentrantLock(true);
    private static ByteBuffer sSharedByteBuffer;

    public static int getSharedByteBufferByteCapacity() {
        final int byteCapacity;
        try {
            SharedMemoryVertexBufferObject.sSharedByteBufferLock.lock();

            final ByteBuffer sharedByteBuffer = SharedMemoryVertexBufferObject.sSharedByteBuffer;
            if (sharedByteBuffer == null) {
                byteCapacity = 0;
            } else {
                byteCapacity = sharedByteBuffer.capacity();
            }
        } finally {
            SharedMemoryVertexBufferObject.sSharedByteBufferLock.unlock();
        }

        return byteCapacity;
    }

    // ===========================================================
    // Fields
    // ===========================================================

    // ===========================================================
    // Constructors
    // ===========================================================

    public SharedMemoryVertexBufferObject(final VertexBufferObjectManager pVertexBufferObjectManager, final int pCapacity, final DrawType pDrawType, final VertexBufferObjectAttributes pVertexBufferObjectAttributes) {
        super(pVertexBufferObjectManager, pCapacity, pDrawType, false, pVertexBufferObjectAttributes);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    @Override
    public void dispose() {
        super.dispose();

        try {
            SharedMemoryVertexBufferObject.sSharedByteBufferLock.lock();

            if (SharedMemoryVertexBufferObject.sSharedByteBuffer != null) {
                BufferUtils.freeDirectByteBuffer(SharedMemoryVertexBufferObject.sSharedByteBuffer);

                SharedMemoryVertexBufferObject.sSharedByteBuffer = null;
            }
        } finally {
            SharedMemoryVertexBufferObject.sSharedByteBufferLock.unlock();
        }
    }

    @Override
    protected ByteBuffer aquireByteBuffer() {
        SharedMemoryVertexBufferObject.sSharedByteBufferLock.lock();

        final int byteCapacity = this.getByteCapacity();

        if (SharedMemoryVertexBufferObject.sSharedByteBuffer == null || SharedMemoryVertexBufferObject.sSharedByteBuffer.capacity() < byteCapacity) {
            if (SharedMemoryVertexBufferObject.sSharedByteBuffer != null) {
                BufferUtils.freeDirectByteBuffer(SharedMemoryVertexBufferObject.sSharedByteBuffer);
            }

            SharedMemoryVertexBufferObject.sSharedByteBuffer = BufferUtils.allocateDirectByteBuffer(byteCapacity);
            SharedMemoryVertexBufferObject.sSharedByteBuffer.order(ByteOrder.nativeOrder());
        }

        SharedMemoryVertexBufferObject.sSharedByteBuffer.limit(byteCapacity);

        return SharedMemoryVertexBufferObject.sSharedByteBuffer;
    }

    @Override
    protected void releaseByteBuffer(final ByteBuffer byteBuffer) {
        SharedMemoryVertexBufferObject.sSharedByteBufferLock.unlock();
    }

    // ===========================================================
    // Methods
    // ===========================================================

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================
}
